##Tam Huynh   W379Z266
##Description: a shutdown timer for Windows. The program is able to set a timer,
##             shutdown at a specific time, or shutdown based on idle network activities
##Please see the README file for the program layout and pseudo-code
        
import tkinter as tk
import psutil
import time
import os
import subprocess
import datetime

class App():
        def __init__(self):
                self.root = tk.Tk()
                self.root.resizable(0,0) #user cannot resize the app
                #center(self.root, 610, 340) #make the app center on the screen
                center(self.root, 480, 252) #make the app center on the screen
                self.root.title("AutoShutdown")
                

                #data
                self.upload = psutil.net_io_counters()[0]
                self.download = psutil.net_io_counters()[1]
                self.remaining_time = 0
                self.force = tk.IntVar()
                self.shutting_down = False
                self.restarting = False
                self.shutdown_id = 0
                self.restart_id = 0

                self.shutdown_str = "Shutdown"
                self.restart_str = "Restart"
                self.logoff_str = "Logoff"

                #menubar
                self.menubar = tk.Menu(self.root)
                menu = tk.Menu(self.menubar, tearoff=0)
                self.menubar.add_cascade(label="File", menu=menu)
                menu.add_command(label="About", command=self.about)
                menu.add_separator()
                menu.add_command(label="Exit", command=self.root.destroy)
                self.root.config(menu=self.menubar)
                
                
                #Current time
                self.current_time_fr = tk.LabelFrame(text="Current Time")
                self.current_time_fr.grid(row=0, column=0, stick='ew', columnspan=2, padx=10, pady=8, ipady=5)
                self.current_time = tk.Label(self.current_time_fr, width=20,text="")
                self.current_time.pack()


                #shutdown timer
                self.shutdown_timer_frame = tk.LabelFrame(text="   Shutdown in...   ")
                self.shutdown_timer_frame.grid(row=1, column=0, stick='w', padx=10, pady=8, ipadx=10, ipady=5)
                self.shutdown_minutes_entry = tk.Entry(self.shutdown_timer_frame, width=8, justify='right')
                self.shutdown_minutes_entry.grid(row=0, column=2, padx=10, stick='e')
                self.shutdown_minutes_entry.insert(0,'10')
                self.shutdown_minutes_label = tk.Label(self.shutdown_timer_frame, text = "minutes")
                self.shutdown_minutes_label.grid(row=0, column=3, stick='w')


                #shutdown at
                self.shutdown_time_frame = tk.LabelFrame(text="   Shutdown at   ")
                self.shutdown_time_frame.grid(row=1, column=1, stick='e', padx=10, pady=8, ipadx=8, ipady=5)
                self.shutdown_time_hour = tk.Spinbox(self.shutdown_time_frame, width=4, from_=0, to=24)
                self.shutdown_time_hour.grid(row=0, column=0, padx=5)
                self.shutdown_time_hour.delete(0)
                self.shutdown_time_hour.insert(0,datetime.datetime.now().strftime("%H"))
                
                self.shutdown_time_colon = tk.Message(self.shutdown_time_frame, text=":")
                self.shutdown_time_colon.grid(row=0, column=1)
                self.shutdown_time_minute = tk.Spinbox(self.shutdown_time_frame, width=4, from_=0, to=60)
                self.shutdown_time_minute.grid(row=0, column=2)
                self.shutdown_time_minute.delete(0)
                self.shutdown_time_minute.insert(0,str(int(datetime.datetime.now().strftime("%M"))+10))

                
                #network status frame
                self.network_frame = tk.LabelFrame(text="Network Status")
                self.network_frame.grid(row=2, column=0, stick='news', columnspan=2, rowspan=4, padx=10, pady=8)


                #upload frame
                self.upload_frame = tk.LabelFrame(self.network_frame, text="   Upload   ")
                self.upload_frame.grid(row=0, column=0, padx=10)
                self.upload_label = tk.Label(self.upload_frame, width=15, text="")
                self.upload_label.pack()

                #download frame
                self.download_frame = tk.LabelFrame(self.network_frame, text="   Download   ")
                self.download_frame.grid(row=0, column=1, padx=10)
                self.download_label = tk.Label(self.download_frame, width=15, text="")
                self.download_label.pack()                               

                #shutdown when network is idle checkbutton
                #currently not functioning
                self.shutdown_idle = tk.Checkbutton(self.network_frame, text="Shutdown after network is idle for 3 minutes")
                self.shutdown_idle.grid(row=1, column=0, columnspan=2, stick='w')

                #program status
                self.status_fr = tk.LabelFrame(text="Program Status")
                self.status_fr.grid(row=0, column=3, stick='ew', padx=10, pady=8, ipady=5)
                self.status = tk.Label(self.status_fr, width = 20, text="IDLE")
                self.status.pack()

                #shutdown icon               
                #self.shutdown_icon = tk.PhotoImage(file="icons/shutdown.gif")
                                
                #shutdown button
                self.shutdown_button = tk.Button(text="Shutdown", font=("Arial", 12), bd=3, width=16, command=self.shutdown_b)
                self.shutdown_button.grid(row=1, column=3, rowspan=1, stick='ns', pady=0)
                #self.shutdown_button.config(image=self.shutdown_icon)
                self.shutdown_button.bind("<Enter>", self.status_bar_shutdown1)
                self.shutdown_button.bind("<Leave>", self.status_bar_shutdown2)
                
                #force shutdown checkbutton
                self.force_shutdown = tk.Checkbutton(text="Force Shutdown", onvalue = 1, offvalue = 0, variable = self.force)
                self.force_shutdown.grid(row=2, column=3, stick='n')                

                #restart icon
                #self.restart_icon = tk.PhotoImage(file="icons/restart.gif")

                #restart button
                self.restart_button = tk.Button(text="Restart", font=("Arial", 10), bd=2, width=16, command=self.restart_b)
                self.restart_button.grid(row=3, column=3, rowspan=1, stick='ns', pady=0)
                self.restart_button.bind("<Enter>", self.status_bar_restart1)
                self.restart_button.bind("<Leave>", self.status_bar_restart2)

                #logoff button
                self.logoff_button = tk.Button(text="Immediate Logoff", font=("Arial", 10), bd=2, width=16, command=self.logoff)
                self.logoff_button.grid(row=4, column=3, rowspan=1, stick='ns', pady=5)
                self.logoff_button.bind("<Enter>", self.status_bar_logoff1)
                self.logoff_button.bind("<Leave>", self.status_bar_logoff2)


                #status bar
                self.status_bar = tk.Label(text = "", bd=1, relief=tk.SUNKEN)
                self.status_bar.grid(row=6, columnspan=6, stick='ew')

                self.update_status()
                self.root.mainloop()


        def status_bar_shutdown1(self, event):
                self.status_bar.config(text=self.shutdown_str)
        def status_bar_shutdown2(self, event):
                self.status_bar.config(text="")
        def status_bar_restart1(self, event):
                self.status_bar.config(text=self.restart_str)
        def status_bar_restart2(self, event):
                self.status_bar.config(text="")
        def status_bar_logoff1(self, event):
                self.status_bar.config(text=self.logoff_str)
        def status_bar_logoff2(self, event):
                self.status_bar.config(text="")

                
        def update_status(self):
                #update current time
                now = time.strftime("%m/%d/%Y  %H:%M:%S")
                self.current_time.configure(text=now)                

                #network polling
                upload1 = psutil.net_io_counters()[0]
                download1 = psutil.net_io_counters()[1]
                self.root.after(500)
                upload2 = psutil.net_io_counters()[0]
                download2 = psutil.net_io_counters()[1]                

                #translate B/s into MB/s
                upload_speed = ((upload2 - upload1) / (1024**2))*2                
                download_speed = ((download2 - download1) / (1024**2))*2
                
                #display current speed
                self.upload_label.configure(text="{:10.2f} MB/s".format(upload_speed))
                self.download_label.configure(text="{:10.2f} MB/s".format(download_speed))

                #refresh after 500ms
                self.root.after(500, self.update_status)


        def shutdown_b(self):                
                if self.shutting_down == False:
                        #check if input entry is valid

                        #spinbox entries
                        sd_at_hour = int(self.shutdown_time_hour.get())
                        sd_at_minute = int(self.shutdown_time_minute.get())
                        sd_time = datetime.time(sd_at_hour, sd_at_minute)
                        difference = datetime.datetime.combine(datetime.date.today(), sd_time) - datetime.datetime.now() 

                        #get whichever entry is sooner
                        if (difference.seconds/60 > int((self.shutdown_minutes_entry.get()))):
                                minutes = int((self.shutdown_minutes_entry.get()))
                        else:
                                minutes = difference.seconds//60
                                
                        #convert from minutes to milliseconds
                        mseconds = minutes * 60 * 1000
                        
                        #change button label to ABORT
                        self.shutdown_button.config(text = "ABORT")

                        #self.delete(shutdown_icon)

                        status_msg = "Shut down in " + str(minutes) + " minutes.."
                        self.status.configure(text=status_msg)

                        self.shutting_down = True
                        self.shutdown_id = self.root.after(mseconds, self.shutdown)

                else: #if shutting down is in process
                        #change button label to Shutdown
                        self.shutdown_button.config(text = "Shutdown")
                        self.status.configure(text="IDLE")

                        #cancel pending root.after
                        self.root.after_cancel(self.shutdown_id)
                        self.shutting_down =  False
                        

        def restart_b(self):                
                if self.restarting == False:
                        #check if input entry is valid
                        try:
                                minutes = int((self.shutdown_minutes_entry.get()))
                                #convert from minutes to milliseconds
                                mseconds = minutes * 60 * 1000
                                
                                #change button label to ABORT
                                self.restart_button.config(text = "ABORT")

                                status_msg = "Restart in " + str(minutes) + " minutes.."
                                self.status.configure(text=status_msg)

                                self.restarting = True
                                self.restart_id = self.root.after(mseconds, self.restart)
                        except: #print ERROR if entry is invalid
                                self.shutdown_minutes_entry.delete(0, tk.END)
                                self.shutdown_minutes_entry.insert(0, "ERROR")

                else: #if restarting is in process
                        #change button label to Restart
                        self.restart_button.config(text = "Restart")
                        self.status.configure(text="IDLE")

                        #cancel pending root.after
                        self.root.after_cancel(self.restart_id)
                        self.restarting =  False

               
        def shutdown(self):
                #check if the "force" checkbutton is on
                if self.force.get() == 1:
                        subprocess.call(["shutdown","/s", "/f"])
                else: #normal shutdown
                        subprocess.call(["shutdown", "/s"])

        def restart(self):                
                subprocess.call(["shutdown","/r"])

        def logoff(self):                
                subprocess.call(["shutdown","/l"])        
                
        def about(self):
                #about button
                about = tk.Toplevel()
                about.resizable(0,0)
                center(about, 210, 160)
                about.title("About")
                about_msg = "AutoShutdown uses the following Python libraries:"
                lib_msg = "tkinter\npsutil\nsubprocess\nos\ntime"
                msg = tk.Message(about, width=200, text = about_msg)
                msg.pack()
                lib = tk.Message(about, text = lib_msg)
                lib.pack()

                #OK button                                 
                button = tk.Button(about, width=10, text="OK", command=about.destroy)
                button.pack()
                

def center(win, _width, _height):
        #This function will calculate the current screen size and display the app in the center of the screen
        width = _width
        height = _height        
        #x = (win.winfo_screenwidth() // 2) - (width // 2) -400
        x = (win.winfo_screenwidth() // 2) - (width // 2)
        y = (win.winfo_screenheight() // 2) - (height // 2)
        win.geometry('{}x{}+{}+{}'.format(width, height, x, y))

def main():
        app=App()

main()
